package ch.ethz.fcl.metrobuzz.algorithm.network.shortest_path.public_transport;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import ch.ethz.fcl.metrobuzz.data.MBData;
import ch.ethz.fcl.metrobuzz.data.scene.basemap.Edge;
import ch.ethz.fcl.metrobuzz.data.scene.basemap.Node;
import ch.ethz.fcl.metrobuzz.data.singapore.SGTransitType;
import ch.ethz.fcl.metrobuzz.data.singapore.SGTransitType.TransitType;
import ch.ethz.fcl.metrobuzz.data.transit.Stop;
import ch.ethz.fcl.metrobuzz.data.transit.TransitRoute;

/**
 * records public transport methods and exchange stops
 * 
 * @author ZengWei
 * 
 */
public class PathPublic {
	private MBData data;

	private List<Route> paths;
	private float cost;
	private float dist;

	private List<List<Node>> exactPath = null;

	public PathPublic(MBData data) {
		this.data = data;
		paths = new ArrayList<Route>();
	}

	public PathPublic(MBData data, PathPublic anotherPath) {
		this(data);

		for (Route r : anotherPath.getPath())
			paths.add(r);
		this.cost = anotherPath.getCost();
	}

	public void addRoute(TransitRoute tr, Stop begin, Stop end, float cost) {
		Route route = new Route(tr, begin, end);

		boolean sameRoute = false;
		if (paths.size() > 0) {
			Route lastRoute = paths.get(paths.size() - 1);
			if (tr != null && lastRoute.getTransitRoute() != null) {
				if (lastRoute.getTransitRoute().getId().equals(tr.getId()))
					sameRoute = true;
			}
		}
		if (sameRoute) {
			route = new Route(tr, paths.get(paths.size() - 1).getBegin(), end);
			paths.remove(paths.size() - 1);
		}

		paths.add(route);
		this.cost += cost;
	}

	public List<Route> getPath() {
		return paths;
	}

	public Route getLastRoute() {
		if (paths.size() == 0)
			return null;
		return paths.get(paths.size() - 1);
	}

	public float getCost() {
		return cost;
	}

	public float getDist() {
		return dist;
	}

	public void setCost(float cost) {
		this.cost = cost;
	}

	public void init(MBData data, Map<Edge, Integer> edgeWeightMap) {
		if (exactPath == null) {
			exactPath = new ArrayList<List<Node>>();
			for (Route route : paths) {
				TransitRoute tr = route.getTransitRoute();
				if (tr != null) {
					if (SGTransitType.getTransitType(tr.getId()) == TransitType.BUS) {
						List<Node> singleRoute = new ArrayList<Node>();

						List<Edge> edges = tr.getInterEdges(route.getBegin(),
								route.getEnd());
						if (edges == null)
							break;

						singleRoute.add(route.getBegin());

						Node start = route.getBegin(), end = edges.get(1)
								.getStartNode();

						Edge edge = data.getScene().getEdge(start, end);
						dist += edge.distance();

						singleRoute.add(edge.getEndNode());

						addEdgeWeight(edgeWeightMap, edge);

						for (int i = 1; i < edges.size() - 1; i++) {
							edge = edges.get(i);

							dist += edge.distance();

							singleRoute.add(edge.getEndNode());

							addEdgeWeight(edgeWeightMap, edge);
						}

						start = edges.get(edges.size() - 1).getStartNode();
						end = route.getEnd();

						edge = data.getScene().getEdge(start, end);
						dist += edge.distance();

						singleRoute.add(route.getEnd());
						addEdgeWeight(edgeWeightMap, edge);

						exactPath.add(singleRoute);
					} else { // MRT lines
						List<Node> singleRoute = new ArrayList<Node>();

						List<Stop> stops = tr.getInterStops(route.getBegin(),
								route.getEnd());
						if (stops == null)
							break;

						for (int i = 0; i < stops.size() - 1; i++) {
							Stop interStop = stops.get(i);
							singleRoute.add(interStop);

							Edge edge = data.getScene().getEdge(stops.get(i),
									stops.get(i + 1));

							dist += edge.distance();

							addEdgeWeight(edgeWeightMap, edge);
						}
						singleRoute.add(stops.get(stops.size() - 1));

						exactPath.add(singleRoute);
					}
				} else { // walk from stop to stop
					List<Node> singleRoute = new ArrayList<Node>();
					singleRoute.add(route.getBegin());
					singleRoute.add(route.getEnd());

					Edge edge = data.getScene().getEdge(route.getBegin(),
							route.getEnd());

					dist += edge.distance();

					addEdgeWeight(edgeWeightMap, edge);

					exactPath.add(singleRoute);
				}
			}
		}
	}

	private void addEdgeWeight(Map<Edge, Integer> edgeWeightMap, Edge edge) {
		if (edgeWeightMap != null) {
			Integer weight = edgeWeightMap.get(edge);
			if (weight == null)
				edgeWeightMap.put(edge, 1);
			else
				edgeWeightMap.put(edge, weight + 1);
		}
	}

	public List<List<Node>> getExactPath() {
		if (exactPath == null)
			init(data, null);
		return exactPath;
	}

	public List<Node> getAllNodes() {
		List<List<Node>> nodeLists = getExactPath();

		List<Node> nodeList = new ArrayList<Node>();
		for (List<Node> nodes : nodeLists)
			for (Node n : nodes)
				if (nodeList.size() == 0
						|| !nodeList.get(nodeList.size() - 1).equals(n))
					nodeList.add(n);

		return nodeList;
	}

	/*
	 * transit route taken between two stops
	 */
	public class Route {
		// transit route, null means walk
		private TransitRoute route;
		private Stop begin, end; // begin and end stops

		public Route() {
		}

		public Route(TransitRoute route, Stop begin, Stop end) {
			this.route = route;
			this.begin = begin;
			this.end = end;
		}

		public void setRoute(TransitRoute route) {
			this.route = route;
		}

		public TransitRoute getTransitRoute() {
			return route;
		}

		public void setBegin(Stop begin) {
			this.begin = begin;
		}

		public Stop getBegin() {
			return begin;
		}

		public void setEnd(Stop end) {
			this.end = end;
		}

		public Stop getEnd() {
			return end;
		}
	}
}
